//
// See the file COPYING for redistribution information.
//

#include <config.h>
#include <iostream>             // for std::cerr
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <Naipax/Common.hpp>
#include <Naipax/Error.hpp>
#include <Naipax/ServiceServer.hpp>

using namespace std;
using namespace Naipax;

ServiceServer::ServiceServer() {
    mOnAccept = NULL;
    _mFd = -1;
    _mIsSetup = false;
}

ServiceServer::~ServiceServer() {
    if (_mFd != -1 && close(_mFd) != 0) {
        NAIPAX_THROW(Error::CANNOT_CLOSE_SOCKET);
    }
}

void ServiceServer::Setup(int32_t Port) {
    if (Port < 0) {
        NAIPAX_THROW(Error::INVALID_ARGUMENT);
    }
    if (_mIsSetup == true) {
        NAIPAX_THROW(Error::SERVER_IS_SETUP);
    }
    
    int32_t ReuseAddr = 1;

    _mFd = socket(PF_INET, SOCK_STREAM, 0);
    if (_mFd < 0) {
        NAIPAX_THROW(Error::CANNOT_OPEN_SOCKET);
    }

    struct sockaddr_in Sin;
    bzero(&Sin, sizeof(Sin));
    Sin.sin_family = AF_INET;
    Sin.sin_port = htons(Port);
    Sin.sin_addr.s_addr = INADDR_ANY;

    if (setsockopt(_mFd, SOL_SOCKET, SO_REUSEADDR, &ReuseAddr,
                   sizeof(ReuseAddr)) == -1) {
        NAIPAX_THROW(Error::CANNOT_SET_SOCKOPTS);
    }

    if (bind(_mFd, (struct sockaddr *) &Sin, sizeof(Sin)) < 0) {
        NAIPAX_THROW(Error::CANNOT_BIND_ADDRESS);
    }

    if (listen(_mFd, 5) < 0) {
        NAIPAX_THROW(Error::CANNOT_LISTEN_ON_SOCK);
    }
    _mIsSetup = true;
}

void ServiceServer::Process(void *pArg) {
    if (_mIsSetup != true) {
        NAIPAX_THROW(Error::SERVER_IS_NOT_SETUP);
    }
    if (_mFd < 0) {
        NAIPAX_THROW(Error::SOCKET_IS_NOT_OPEN);
    }
    if (mOnAccept == NULL) {
        return;
    }

    int32_t ClientFd = 0;
    struct sockaddr_in ClientAddr;
    socklen_t ClientAddrSize = sizeof(ClientAddr);
    
    ClientFd = accept(_mFd, (struct sockaddr *)&ClientAddr, &ClientAddrSize);
    if (ClientFd < 0) {
        NAIPAX_THROW(Error::CANNOT_ACCEPT_CONNECTION);
    }
    
    (*mOnAccept)(ClientFd, pArg);
}
